/**
 *
 */
package org.jprobe.codegen;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import org.jprobe.classpath.Instantiator;
import org.jprobe.classpath.InstantiatorInterface;

/**
 * BeanInspectorInterface implementation
 * @author Hussain Al-Mutawa
 * @version 2.0
 * @since 1.0
 */
public class BeanInspector implements BeanInspectorInterface {
	//	/** classpath of the project **/
	//	private transient static final ClassPathInterface CLASSPATH=ClassPathReader.instance.classpath;
	private static final InstantiatorInterface INSTANTIATOR = new Instantiator();

	//	/** the project hosting the bean **/
	//	private transient ProjectInterface project;
	/** the bean type which is going to be inspected **/
	private transient Class<?> bean;
	/** an instantiated copy of the bean **/
	private transient Object beanInstance;
	/** a table of the values gained from each getter **/
	private transient final SortedMap<Method, BeanGetterValueInterface>publicMethodsValues;
	/** private/protected/default access methods **/
	private transient final SortedMap<Method, BeanGetterValueInterface>nonPublicMethodsValues;

	private transient final Set<Method> voidMethods;
	/** initializes the bean inspector **/
	public BeanInspector() {
		super();
		final Comparator<Method> comprator = MethodsComparator.instance();
		this.publicMethodsValues=new TreeMap<Method, BeanGetterValueInterface>(comprator);
		this.nonPublicMethodsValues=new TreeMap<Method, BeanGetterValueInterface>(comprator);
		this.voidMethods=new HashSet<Method>();
	}

	@Override
	public void inspect() throws Exception {
		if(beanInstance==null){
			beanInstance = INSTANTIATOR.getInitializedObject(bean);
		}
		this.publicMethodsValues.clear();
		this.nonPublicMethodsValues.clear();
		final Set<Method>methods=new HashSet<Method>();
		methods.addAll(Arrays.asList(bean.getDeclaredMethods()));
		methods.addAll(Arrays.asList(bean.getMethods()));
		//System.out.println(methods.size());

		for(final Method method:methods){
			System.out.println(" ~ inspecting "+ method +"]");
			if( isNonAbstractNotDefinedByObject(method) ){
				if(method.getReturnType()==Void.TYPE){
					voidMethods.add(method);
				}else{
					this.inspectMethod(method);
				}
			}
		}
	}

	protected void inspectMethod(Method method) {
		//System.out.println(method);
		final BeanGetterValueInterface returnValue = new BeanGetterValue();
		try{
			final Object[] parameters = INSTANTIATOR.getParametersValues(method.getParameterTypes());
			method.setAccessible(true);

			returnValue.setValue(method.invoke(beanInstance, parameters));
		}catch(final Throwable exp){
			if(exp.getCause()==null){
				returnValue.setExcepionThrown( exp.getClass().getName() );
			}else{
				returnValue.setExcepionThrown( exp.getCause().getClass().getName() );
			}
		}
		if(Modifier.isPublic(method.getModifiers())){
			this.publicMethodsValues.put( method , returnValue );
		}else{
			this.nonPublicMethodsValues.put( method , returnValue );
		}
	}

	/**
	 * determines if the method is non-abstract and returns a value
	 * @param method the method to check its modifiers
	 * @return true if the method is declared public (return value) and has definition, false otherwise
	 */
	public static boolean isNonAbstractNotDefinedByObject(final Method method){
		final int modifiers = method.getModifiers();
		return !Modifier.isAbstract(modifiers)
		//&&  method.getReturnType()!=Void.TYPE
		&&  method.getDeclaringClass()!=Object.class
		&&  !("$VRi".equals(method.getName()) && method.toString().contains("boolean[][]"));
	}


	@Override
	public Set<Method> getVoidMethods(){
		return voidMethods;
	}

	@Override
	public void reset() {
		this.publicMethodsValues.clear();
		this.beanInstance = null;
	}

	@Override
	public Class<?>getBean(){
		return bean;
	}

	@Override
	public void setBean(final Class<?>bean){
		this.bean=bean;
		this.reset();
	}

	@Override
	public BeanGetterValueInterface get(final Method method) {
		final BeanGetterValueInterface result;
		if(publicMethodsValues.containsKey(method)){
			result = this.publicMethodsValues.get(method);
		}else{
			result = this.nonPublicMethodsValues.get(method);
		}
		return result;
	}
	/**
	 * gets the bean instance
	 * @return bean instance
	 */
	public Object getBeanInstance() {
		return beanInstance;
	}

	@Override
	public Method[] getPublicMethods() {
		return publicMethodsValues.keySet().toArray(new Method[publicMethodsValues.size()]);
	}

	@Override
	public Method[] getPrivateMethods() {
		return this.nonPublicMethodsValues.keySet().toArray(new Method[this.nonPublicMethodsValues.size()]);
	}

	@Override
	public Method[] getGettersMethods() {
		final List<Method>list=new LinkedList<Method>();
		list.addAll(publicMethodsValues.keySet());
		list.addAll(nonPublicMethodsValues.keySet());
		return list.toArray(new Method[list.size()]);
	}
	@Override
	public BeanGetterValueInterface get(final String methodToString) {
		BeanGetterValueInterface bgv = null;
		final Method[] methods=getGettersMethods();
		for(final Method method:methods){
			if(methodToString.equals(method.toString())){
				bgv=get(method);
				break;
			}
		}
		return bgv;
	}

	public void setBeanInstance(final Object beanInstance) {
		this.beanInstance = beanInstance;
	}
}
